
// bind to window event
window.addEventListener('load', onloadHandler, false);

/**
 * Global window onload handler
 */
function onloadHandler()
{
    // Gets a references to the 2d context from the canvas
    ArmageddonGlobals.init();
    ArmageddonGame.setDrawContext(ArmageddonGlobals.drawContext);

    // ArmageddonGame draws itself every 20 ms
    setInterval(ArmageddonGame.onFrame, 20);
    
    // Bind to key down/key up events
    document.onkeydown = function(event)
    {
        var keyCode = (event === null ? window.event.keyCode : event.keyCode);
        ArmageddonGame.handleKeyDown(keyCode);
    };
    
    document.onkeyup = function(event)
    {
        var keyCode = (event === null ? window.event.keyCode : event.keyCode);
        ArmageddonGame.handleKeyUp(keyCode);
    }
}

// Represents a moving object that has position and velocity.
// Currently can be: Asteroid, Shot or Player.
function ArmageddonScreenObject()
{
    this.position = new Vector();
    this.velocity = new Vector();
}

// All moving objects are currently represented as a white drawCircle.
ArmageddonScreenObject.prototype.draw = function()
{
    drawCircle(this.position.x, this.position.y, this.getRadius());
}

ArmageddonScreenObject.prototype.getRadius = function() 
{
    return 0; 
};

// Occurs on a game frame: updates the position of a moving object
// according to this.velocity.
ArmageddonScreenObject.prototype.updatePosition = function() 
{
    this.position.x += this.velocity.x;
    this.position.y += this.velocity.y;

    // Make sure that the object stays in bounds of the screen.
    if (this.position.x >= ArmageddonGlobals.width)
    {
        this.position.x = ArmageddonGlobals.width;
        this.velocity.x *= -1;
    }
    if (this.position.x <= 0)
    {
        this.position.x = 0;
        this.velocity.x *= -1;
    }
}

ArmageddonScreenObject.prototype.remove = function()
{
    ArmageddonGame.removeScreenObject(this);
}


Asteroid.prototype = new ArmageddonScreenObject();

function Asteroid(initialSize)
{
    this.position = GetRandomVector(0, ArmageddonGlobals.width, 0, ArmageddonGlobals.height / 2);
    this.size = initialSize;

    // Maximum velocity for Asteroid: a big one is slower than a small one.
    var maxVelocity = this.size == 3 ? 2  // largest: maximum velocity 2
    : this.size == 2 ? 4
    : 6;                // smallest: maximum velocity 6
                    
       
    this.velocity = GetRandomVector(maxVelocity * -1, maxVelocity, maxVelocity * -1, maxVelocity);
    while (this.velocity.y == 0 && this.velocity.x == 0)
    {
        this.velocity = GetRandomVector(maxVelocity * -1, maxVelocity * -1, maxVelocity * -1, maxVelocity * -1);
    }
    
    ArmageddonGame.addAsteroid(this);
}

Asteroid.prototype.constructor = Asteroid;

Asteroid.prototype.getRadius = function()
{
    var radius = this.size == 3 ? 25 
               : this.size == 2 ? 15 
                                : 10;
               
    return radius;
}

Asteroid.prototype.updatePosition = function()
{
    ArmageddonScreenObject.prototype.updatePosition.call(this);
    
    // When an Asteroid hits the top it bounces off
    if (this.position.y <= 0)
    {
        this.position.y = 0;
        this.velocity.y *= -1;
    }

    // When an Asteroid gets off the bottom it is removed and the
    // player loses points.
    if (this.position.y >= ArmageddonGlobals.height + 20)
    {
        // The player loses more points if the Asteroid is big
        ArmageddonGame.getPlayer().changeScore(-200 * this.size, this);
        this.remove();
    }
}

Asteroid.prototype.remove = function()
{
    // Remove off the screen objects list
    ArmageddonScreenObject.prototype.remove.call(this);

    // Also remove from the asteroids list
    ArmageddonGame.removeAsteroid(this);
}

//Asteroid.prototype.draw = function()
//{
//    drawCircle(this.position.x, this.position.y, this.getRadius());
//}

Shot.prototype = new ArmageddonScreenObject();
Shot.prototype.constructor = Shot;

function Shot(player)
{
    // A shot gets positioned at the player's position
    this.position = new Vector();
    this.position.x = player.position.x;
    this.position.y = player.position.y;
    
    // With an upward speed of 7
    this.velocity.y = -7;
    
    ArmageddonGame.addShot(this);
}

Shot.prototype.getRadius = function()
{
    return 1;
}

Shot.prototype.remove = function()
{
    // Remove off the screen objects list
    ArmageddonScreenObject.prototype.remove.call(this);

    ArmageddonGame.removeShot(this);
}

Shot.prototype.updatePosition = function()
{
    ArmageddonScreenObject.prototype.updatePosition.call(this);
    
    // A shot gets removed as it gets off the top of the screen
    if (this.position.y <= 0)
    {
        this.remove();
    }
}


Player.prototype = new ArmageddonScreenObject();
Player.prototype.constructor = Player;

function Player()
{
    // Start at the middle of the screen
    this.position.x = ArmageddonGlobals.width / 2;
    this.position.y = ArmageddonGlobals.height - 20;
    
    // Keep the last shot time to enforce an interval between shots
    this.lastShotTime = Date.now();
    
    // Start with 3 lives, and 0 points
    this.lives = 3;
    this.score = 0;
    this.topScore = 0;
}

Player.prototype.changeScore = function(score, changingObject)
{
    // Add/reduce the score
    this.score += score;
    
    // Make sure no negative score
    if (this.score < 0) this.score = 0;
    
    // Show a text popup at the changing object's position (e.g. an asteroid getting hit)
    ArmageddonGame.showTextPopup(new AnimatedTextPopup(changingObject.position, score));
    
    // keep a record of the top score to notice when a new 10,000 is entered
    // for the 1 up in life!
    if (this.topScore < this.score) 
    {
        var scoreBefore = this.topScore;
        this.topScore = this.score;
        if (this.topScore % 10000 < scoreBefore % 10000)
        {
            // Add a life to the player
            this.lives++;

            // Display a message right below the Score
            var oneupPosition = 
            {
                x: ArmageddonGlobals.width - 200, 
                y: 80
            };
            
            ArmageddonGame.showTextPopup(new AnimatedTextPopup(oneupPosition, "1 up!", 32, 2000));
            
        }
    }
}

Player.prototype.getRadius = function()
{
    return 7;
}

Player.prototype.changeVelocityByKeys = function(leftPressed, rightPressed)
{
    // Left/right keys change velocity and not position
    // directly, for an acceleration/decelaration effect
    if (leftPressed)
    {
        if (this.velocity.x > 0)
            this.velocity.x = 0;
        this.velocity.x--;
    }
    else if (rightPressed)
    {
        if (this.velocity.x < 0)
            this.velocity.x = 0;
        this.velocity.x++;
    }
    else
    {
        // Slow down until stop when arrow keys are not pressed
        if (this.velocity.x != 0)
        {
            if (this.velocity.x > 0) this.velocity.x--;
            if (this.velocity.x < 0) this.velocity.x++;
        }    
    }
    
    // Make sure   -11 <= velocity.x <= 11
    var maxVelocity = 11;
    this.velocity.x = Math.max(Math.min(this.velocity.x, maxVelocity ), maxVelocity * -1);
    
}
    
function Vector()
{
    this.x = 0;
    this.y = 0;
}

Vector.prototype.distance = function(other)
{
    // Points can measure distances between each other
    var dX = other.x - this.x;
    var dY = other.y - this.y;
    return Math.sqrt(dX*dX + dY*dY);
}

function GetRandomVector(minX, maxX, minY, maxY)
{
    var vector = new Vector();
    
    vector.x = (Math.random() * (maxX - minX) + 1) + minX;
    vector.y = (Math.random() * (maxY - minY) + 1) + minY;
    
    return vector;
}

function RemoveFromList(list, item)
{
    var idx = list.indexOf(item);
    list.splice(idx, 1);
}


function AnimatedTextPopup(position, text, fontSize, duration, title)
{
    this.textPopupTime = Date.now();
    this.position =
    {
        x: position.x,
        y: Math.min(position.y, ArmageddonGlobals.height - 15)
    };
    this.text = text;
    
    if (title)
        this.title = title;
    
 
    this.fontSize = fontSize ? fontSize : 15;
    this.duration = duration ? duration : 1000;
}


var KEY = {
    SHIFT:16, 
    CTRL:17, 
    ESC:27, 
    RIGHT:39, 
    UP:38, 
    LEFT:37, 
    DOWN:40, 
    SPACE:32,
    A:65, 
    E:69, 
    G:71, 
    L:76, 
    P:80, 
    R:82, 
    S:83, 
    Z:90
};


var ArmageddonGame = (function()
{
    var startNewGame = true;
    var waitingForAKeyPress = true;
    var currentScreenObjects = [];
    var currentShots = [];
    var currentAsteroids = [];
    var currentTextPopups = [];
    var gameOver = false;
    var player = {};
    var level = 1;
    var cutSceneInProgress = false;
    var cutSceneStartTime = null;
    var levelFinishTime = null;

    var leftPressed = false;
    var rightPressed = false;
    var spacePressed = false;
    var paused = false;
    
    var ctx = null;
    
    function handleKey (keyCode, direction)
    {
        if (waitingForAKeyPress)
        {
            waitingForAKeyPress = false;
        }
        
        if (keyCode == KEY.LEFT)
        {
            leftPressed = (direction == "down");
        }
        if (keyCode == KEY.RIGHT)
        {
            rightPressed = (direction == "down");
        }
        if (keyCode == KEY.SPACE)
        {
            spacePressed = (direction == "down");
        }
        if (keyCode == KEY.ESC && (direction == "up"))
        {
            paused = !paused;
        }
    }
    
    function drawGameFrame ()
    {
        clearCanvas();

        if (startNewGame)
        {
            currentScreenObjects = [];
            currentAsteroids = [];
            currentShots = [];
        
            level = 1;
        
            cutSceneInProgress = true;
            cutSceneStartTime = Date.now();
        
            player = new Player();
        
            currentScreenObjects.push(player);
        }
        
        if (waitingForAKeyPress)
        {
            drawText("ARMAGEDDON", "40pt Courier New", 50, ArmageddonGlobals.height / 2 - 50, "#ff0000");
            drawText("Asteroids Invaders", "25pt Arial", 50, ArmageddonGlobals.height / 2, "#ff0000");
            if (Date.now() % 2000 < 1000)
            {
                drawText("Press any key to start...", "20pt Arial", 50, ArmageddonGlobals.height / 2 + 50, "#ff0000");
            }
            return;
        }
    
        startNewGame = false;


        if (cutSceneInProgress)
        {
            var cutSceneElapsed = Date.now() - cutSceneStartTime;
            var cutSceneShouldEnd = cutSceneElapsed > 1500;
            
            if (!cutSceneShouldEnd)
            {
                var text = gameOver ? "Game Over" 
                                    : "Level " + level;
                drawText(text, "32pt Arial", 100, ArmageddonGlobals.height / 2, "#ff0000", true);
                return;
            }

            // cutSceneShouldEnd
        
            if (gameOver)
            {
                // After Game Over cutscene is finished, start a new game
                gameOver = false;
                startNewGame = true;
                waitingForAKeyPress = true;
                cutSceneStartTime = Date.now();
                return;
            }

            // After every other cutscene is over (between levels),
            // initialize next level
            while (currentShots.length > 0)
            {
                currentShots[0].remove();
            }
        
            var amountOfLargeAsteroidsInLevel = level;
            var amountOfMediumAsteroidsInLevel = Math.floor(level / 2);
            var amountOfSmallAsteroidsInLevel = Math.floor(level / 3);
            
            for (var i = 0; i < amountOfLargeAsteroidsInLevel; i++)
            {
                var asteroid = new Asteroid(3);
            }

            for (var i = 0; i < amountOfMediumAsteroidsInLevel; i++)
            {
                var asteroid = new Asteroid(2);
            }

            for (var i = 0; i < amountOfSmallAsteroidsInLevel; i++)
            {
                var asteroid = new Asteroid(1);
            }

            cutSceneStartTime = Date.now();
            cutSceneInProgress = false;
            
        }
    
        // Game logic:
        if (!paused)
        {
            // Control player velocity by keyboard
            player.changeVelocityByKeys(leftPressed, rightPressed);

            // Fire shots by space bar
            if (spacePressed)
            {
                var canFireWeapon = Date.now() - player.lastShotTime > 100;
                
                if (player.position.x > ArmageddonGlobals.width)
                {
                    // Hack: in case player is off-screen, don't fire weapons
                    canFireWeapon = false;
                }
                
                if (canFireWeapon)
                {
                    var shot = new Shot(player);
                    player.lastShotTime = Date.now();
                }
            }

            // Detect if any existing shots hit asteroids
            detectShotsHitAsteroids();
    
            // Tell all moving objects to update themselves
            for (var i = 0; i < currentScreenObjects.length; i++)
            {
                var screenObject = currentScreenObjects[i];
                screenObject.updatePosition();
            }

            // Handle life down if player is hit
            if (playerCollidesWithAsteroid())
            {
                if (player.lives > 0)
                {
                    player.lives--;
                    player.liveLostTime = Date.now();
                    currentTextPopups.push(new AnimatedTextPopup({x: 100, y: ArmageddonGlobals.height / 2 - 50}, "Ouch you're down", 20, 2500));
                }
            }
        }
    
        // Display text popups (score changes) on screen
        for (var i = 0; i < currentTextPopups.length; i++)
        {
            var textPopup = currentTextPopups[i];
            if (Date.now() - textPopup.textPopupTime < textPopup.duration)
            {
                var size = textPopup.fontSize;
                if (!textPopup.title)
                    size = Math.min(Math.floor((Date.now() - textPopup.textPopupTime) * size / 300), size);
                drawText(textPopup.text, size + "pt Arial", textPopup.position.x, textPopup.position.y, "#ff0000", textPopup.title);
            }
            else
            {
                currentTextPopups.splice(i, 1);
                i--;
            }
        }
    
        // Wait for some time before player is respawned
        if (player.liveLostTime)
        {
            var downMessageTime = (player.lives > 0) ? 600 : 1500;
            
            if (Date.now() - player.liveLostTime < downMessageTime)
            {
                // temporarily put the player off screen
                player.position.x = 1000;
                player.position.y = 1000;
            }
            else 
            {
                // After waiting is finished: either respawn or game over
                
                // Respawn: put in middle of screen
                if (player.lives > 0)
                {
                    player.position.x = ArmageddonGlobals.width / 2;
                    player.position.y = ArmageddonGlobals.height - 20;
            
                    if (playerCollidesWithAsteroid())
                    {
                        // But wait in case there's an asteroid there
                        player.position.x = 1000;
                        player.position.y = 1000;
                    }
                    else
                    {
                        player.liveLostTime = null;
                    }
                }
                else
                {
                    // Game over: show Game Over message next frame
                    gameOver = true;
                    cutSceneInProgress = true;
                    cutSceneStartTime  = Date.now();
                }
            }
        }
   
        // Draw all moving objects
        for (var i = 0; i < currentScreenObjects.length; i++)
        {
            var screenObject = currentScreenObjects[i];
            drawCircle(screenObject.position.x, screenObject.position.y, screenObject.getRadius());
            //screenObject.draw();       
        }
    
        // Check if level is finished
        if (currentAsteroids.length == 0)
        {
            if (!levelFinishTime)
            {
                levelFinishTime = Date.now();
                currentTextPopups.push(new AnimatedTextPopup({x: 100, y: ArmageddonGlobals.height / 2}, "Level clear.", 20, 2500, true));
            }
            else if (Date.now() - levelFinishTime > 2000)
            {
                level++;
                cutSceneInProgress = true;
                cutSceneStartTime = Date.now();
                levelFinishTime = null;
            }
        
        }
    
        drawText("Lives: " + player.lives, "20pt Arial", 10, 30, "#ff0000");
        drawText("Score: " + player.score, "20pt Arial", ArmageddonGlobals.width - 200, 30, "#ff0000");
        //    drawText("Top score: " + player.topScore, "20pt Arial", ArmageddonGlobals.width - 200, 80, "#ff0000");
        drawText("Level: " + level, "20pt Arial", 10, 80, "#ff0000");
    
        if (paused)
        {
            drawText("PAUSED", "32pt Arial", 100, ArmageddonGlobals.height / 2, "#ff0000");
            drawText("Pres ESC to continue.", "20pt Arial", 100, ArmageddonGlobals.height / 2 + 50, "#ff0000");
        }
    }

    function detectShotsHitAsteroids()
    {
        for (var i = 0; i < currentShots.length; i++)
        {
            var shot = currentShots[i];
            for (var j = 0; j < currentAsteroids.length; j++)
            {
                var asteroid = currentAsteroids[j];
                if (asteroid && shot)
                {
                    if (collision(asteroid, shot))
                    {
                        shot.remove();
                        asteroidGotShot(asteroid);
                
                        i--;
                        j--;
                        break;
                    }
                }
            }
        }
    }
 
    function asteroidGotShot(asteroid)
    {
        // When an Asteroid is hit by a Shot, the player gets points
        player.changeScore((4 - asteroid.size) * 100, asteroid);

        // The asteroid is removed
        asteroid.remove();

        // And if the asteroid is big, two smaller ones are created instead
        if (asteroid.size > 1)
        {
            for (var k = 0; k < 2; k++)
            {
                var spawnedAsteroid = new Asteroid(asteroid.size - 1);
                spawnedAsteroid.position.x = asteroid.position.x;
                spawnedAsteroid.position.y = asteroid.position.y;
            }
        }
    }


    function playerCollidesWithAsteroid()
    {
        for (var i = 0; i < currentAsteroids.length; i++)
        {
            var asteroid = currentAsteroids[i];
            if (collision(asteroid, player))
            {
                return true;
            }
        }

        return false;
    }

    function collision(objA, objB)
    {
        var distance = objA.position.distance(objB.position);
        var crashDistance = objA.getRadius() + objB.getRadius();
        return distance <= crashDistance;
    }

    function drawText(txt, font, x, y, col, title)
    {
        ctx.save();
        if (col && !title) ctx.fillStyle = col;
        if (col && title) ctx.strokeStyle = col;
        ctx.font = font;
        ctx.lineWidth = 2;
        if (title)
            ctx.strokeText(txt, x, y);
        else 
            ctx.fillText(txt, x, y);
        ctx.restore();
    }

    function drawTextTitle(txt, font, xpos, ypos, col)
    {
        var mult = Date.now() % 256;
         for (var i=0, j=txt.length; i<j; i++)
         {
            drawText(txt[i], font, xpos + i*30, ypos, mult.toString(16), "#ffffff");
         }
    }

    function clearCanvas() 
    {
        ctx.clearRect(0, 0, ArmageddonGlobals.width,ArmageddonGlobals.height);
    }

    function drawCircle(x,y,r) 
    {
        ctx.beginPath();
        ctx.arc(x, y, r, 0, Math.PI*2, true);
        ctx.closePath();
        ctx.lineWidth = 5;
        ctx.strokeStyle = "white";
        ctx.stroke();
    }

    return {
        onFrame: function () {
            drawGameFrame();
        },
        handleKeyDown: function(keyCode) 
        {
            handleKey(keyCode, "down");
        },
        
        handleKeyUp: function(keyCode) 
        {
            handleKey(keyCode, "up");
        },
        
        addAsteroid: function(asteroid)
        {
            // The Asteroid adds itself to the current objects list
            currentScreenObjects.push(asteroid);
            currentAsteroids.push(asteroid);
        },

        removeScreenObject: function(screenObject)
        {
            RemoveFromList(currentScreenObjects, screenObject);
        },
 
        removeAsteroid: function(asteroid)
        {
            RemoveFromList(currentAsteroids, asteroid);
        },
        
        addShot: function(shot)
        {
            currentScreenObjects.push(shot);
            currentShots.push(shot);
        },

        removeShot: function(shot)
        {
            RemoveFromList(currentShots, shot);
        },
        
        showTextPopup: function(textPopup)
        {
            currentTextPopups.push(textPopup);
        },
        
        getPlayer: function() {return player;},
        
        setDrawContext: function(drawContext)
        {
            ctx = drawContext;
        }

    };

    
})();


var ArmageddonGlobals =
{
    /**
     * The single canvas play field element reference
     */
    canvas: null,
    
    // The 2d context
    drawContext: null,
   
    /**
     * Width of the canvas play field
     */
    width: 0,
   
    /**
     * Height of the canvas play field
     */
    height: 0,

    /**
     * Init function called once by your window.onload handler
     */
    init: function()
    {
        this.canvas = document.getElementById('canvas');
        this.width = this.canvas.height;
        this.height = this.canvas.width;
        this.drawContext = ArmageddonGlobals.canvas.getContext('2d');
    }
}